package com.bugsnag.ohos;

import com.bugsnag.ohos.util.FileUtils;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import java.util.Map;

import static com.bugsnag.ohos.DeliveryHeaders.HEADER_BUGSNAG_INTEGRITY;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT;
import static java.net.HttpURLConnection.HTTP_OK;

/**
 * DefaultDelivery
 *
 * @since 2021-07-01
 */
public class DefaultDelivery implements Delivery {
    Connectivity connectivity;
    Logger logger;

    /**
     * constructor
     *
     * @param connectivity
     * @param logger
     */
    public DefaultDelivery(Connectivity connectivity, Logger logger) {
        this.connectivity = connectivity;
        this.logger = logger;
    }

    @Override
    public DeliveryStatus deliver(Session payload, DeliveryParams deliveryParams) {
        DeliveryStatus status = deliver(deliveryParams.endpoint, payload, deliveryParams.headers);
        logger.i("Session API request finished with status $status");
        return status;
    }

    @Override
    public DeliveryStatus deliver(EventPayload payload, DeliveryParams deliveryParams) {
        DeliveryStatus status = deliver(deliveryParams.endpoint, payload, deliveryParams.headers);
        logger.i("Error API request finished with status " + status);
        return status;
    }

    /**
     * deliver
     *
     * @param urlString
     * @param streamable
     * @param headers
     * @return DeliveryStatus
     */
    public DeliveryStatus deliver(String urlString,
                                  JsonStream.Streamable streamable,
                                  Map<String, String> headers) {
        logger.d("deliver urlString = " + urlString);
        if (connectivity != null && !connectivity.hasNetworkConnection()) {
            return DeliveryStatus.UNDELIVERED;
        }
        HttpURLConnection conn = null;
        try {
            byte[] json = serializeJsonPayload(streamable);
            logger.d("deliver makeRequest start ");
            conn = makeRequest(new URL(urlString), json, headers);
            logger.d("deliver makeRequest end ");
            int responseCode = conn.getResponseCode();
            DeliveryStatus status = getDeliveryStatus(responseCode);
            logRequestInfo(responseCode, conn, status);
            return status;
        } catch (OutOfMemoryError oom) {
            logger.w("Encountered OOM delivering payload, falling back to persist on disk", oom);
            return DeliveryStatus.UNDELIVERED;
        } catch (IOException exception) {
            logger.w("IOException encountered in request", exception);
            return DeliveryStatus.UNDELIVERED;
        } catch (Exception exception) {
            logger.w("Unexpected error delivering payload %s", exception);
            return DeliveryStatus.FAILURE;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    /**
     * Converts a [JsonStream.Streamable] into JSON, placing it in a [ByteArray]
     *
     * @param streamable
     * @return byte data
     */
    public byte[] serializeJsonPayload(JsonStream.Streamable streamable) {
        logger.d("deliver serializeJsonPayload（） ");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PrintWriter printWriter = new PrintWriter(byteArrayOutputStream);
        BufferedWriter bufferedWriter = new BufferedWriter(printWriter, 8 * 1024);
        JsonStream jsonStream = new JsonStream(bufferedWriter);
        logger.d("deliver serializeJsonPayload 1 " + streamable);
        try {
            logger.d("deliver serializeJsonPayload 21");
            streamable.toStream(jsonStream);
            logger.d("deliver serializeJsonPayload 22");
        } catch (IOException e) {
            logger.d("deliver serializeJsonPayload 3");
        } finally {
            try {
                jsonStream.close();
                logger.d("deliver serializeJsonPayload 4");
            } catch (IOException e) {
                logger.d("deliver serializeJsonPayload 5" + e.toString());
            }
        }
        logger.d("deliver serializeJsonPayload end ");
        return byteArrayOutputStream.toByteArray();
    }

    private HttpURLConnection makeRequest(
        URL url, byte[] json, Map<String, String> headers) {
        logger.d("deliver makeRequest start json size:" + json.length);
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
        } catch (IOException e) {
            e.getMessage();
        }

        String com = computeSha1Digest(json);
        logger.d("deliver makeRequest com:" + com);
        if (com != null) {
          //  conn.addRequestProperty(HEADER_BUGSNAG_INTEGRITY, com);
        }

        for (String key : headers.keySet()) {
            if (headers.get(key) != null) {
                conn.addRequestProperty(key, headers.get(key));
            }
        }
        conn.setChunkedStreamingMode(0);
        conn.addRequestProperty("Content-Type", "application/json");
        OutputStream outputStream = null;
        try {
            outputStream = conn.getOutputStream();
            outputStream.write(json);
        } catch (IOException e) {
            e.getMessage();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.getMessage();
                }
            }
        }
        return conn;
    }

    String computeSha1Digest(byte[] payload) {
        try {
            MessageDigest shaDigest = MessageDigest.getInstance("SHA-1");
            StringBuilder builder = new StringBuilder("sha1 ");
            DigestOutputStream digestOutputStream = new DigestOutputStream(new NullOutputStream(), shaDigest);
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(digestOutputStream, 8 * 1024);
            bufferedOutputStream.write(payload);

            byte[] digest = shaDigest.digest();
            for (byte b : digest) {
                builder.append(String.format(Locale.getDefault(), "%02x", b));
            }
            return builder.toString();
        } catch (NoSuchAlgorithmException | IOException e) {
            e.getMessage();
            return null;
        }
    }

    /**
     * NullOutputStream
     *
     * @since 2021-07-26
     */
    class NullOutputStream extends OutputStream {
        @Override
        public void write(int i) {
        }
    }

    DeliveryStatus getDeliveryStatus(int responseCode) {
        if (responseCode >= HTTP_OK && responseCode <= 299) {
            return DeliveryStatus.DELIVERED;
        } else if (responseCode >= HTTP_BAD_REQUEST && responseCode <= 499
            && responseCode != HTTP_CLIENT_TIMEOUT && responseCode != 429) {
            return DeliveryStatus.FAILURE;
        } else {
            return DeliveryStatus.UNDELIVERED;
        }
    }

    private void logRequestInfo(int code, HttpURLConnection conn, DeliveryStatus status) {
        String responseMessage = "";
        try {
            responseMessage = conn.getResponseMessage();
        } catch (IOException e) {
            e.getMessage();
        }
        logger.i(
            "Request completed with code  " + code
                + ",message:   " + responseMessage
                + ",headers:  " + conn.getHeaderFields());
        InputStream inputStream;
        try {
            inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));

            BufferedReader bufferedReader = new BufferedReader(inputStreamReader, 8 * 1024);
            StringWriter buffer = new StringWriter();

            FileUtils.copyTo(bufferedReader, buffer);
            logger.d("Received request response:" + buffer.toString());
        } catch (IOException e) {
            e.getMessage();
        }

        if (status != DeliveryStatus.DELIVERED) {
            InputStream errorStream = conn.getErrorStream();
            InputStreamReader inputStreamReader = new InputStreamReader(errorStream, Charset.forName("UTF-8"));
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader, 8 * 1024);
            StringWriter buffer = new StringWriter();
            FileUtils.copyTo(bufferedReader, buffer);
            logger.d("Request error details:" + buffer.toString());
        }
    }
}
